package com.vision.sales.sv.serv.rpc;

import static com.vision.sales.cm.data.SalesPermissions.CreateCustomer;
import static com.vision.sales.cm.data.SalesPermissions.DeleteCustomer;
import static com.vision.sales.cm.data.SalesPermissions.UpdateCustomer;
import static com.vision.sales.cm.data.SalesPermissions.ViewCustomer;

import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import com.sencha.gxt.data.shared.loader.PagingLoadResult;
import com.sencha.gxt.data.shared.loader.PagingLoadResultBean;
import com.vision.core.cm.data.Domain;
import com.vision.core.cm.exception.RpcException;
import com.vision.core.sv.db.mgr.IDataManager;
import com.vision.core.sv.db.mgr.ISimpleCriteria;
import com.vision.core.sv.db.mgr.SimpleCriteria;
import com.vision.core.sv.serv.rpc.AbstractServiceImpl;
import com.vision.core.sv.util.CollectionUtils;
import com.vision.core.sv.util.SimpleTransformer;
import com.vision.sales.cl.ui.component.TransactionLoadConfig;
import com.vision.sales.cm.data.CollectionStatus;
import com.vision.sales.cm.data.TransactionType;
import com.vision.sales.cm.db.data.Customer;
import com.vision.sales.cm.db.data.SalesInvoice;
import com.vision.sales.cm.db.data.TransactionEntry;
import com.vision.sales.cm.serv.rpc.CustomerService;
import com.vision.sales.sv.util.TransactionHelper;

/**
 * 
 * @author Mark
 *
 */
@Service("customers")
public class CustomerServiceImpl extends AbstractServiceImpl<Customer> implements CustomerService {
	
	private static final Logger log = LoggerFactory.getLogger(CustomerServiceImpl.class);

	private static final long serialVersionUID = 1L;
	
	@Autowired
	@Qualifier("salesInvoiceManager")
	private IDataManager<SalesInvoice> invoiceManager;
	
	public CustomerServiceImpl() {
		initGilead();
	}
	
	@Autowired
	@Qualifier("customerManager")
	@Override
	public void setDataManager(IDataManager<Customer> dataManager) {
		super.setDataManager(dataManager);
	}
	
	@Override
	protected Domain getDomain() {
		return Domain.CUSTOMER;
	}
	
	// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
	
	@Override
	public List<Customer> getAll() throws RpcException {
		checkPermissions(ViewCustomer);
		
		return super.getAll();
	}

	@Override
	public Customer create(Customer object) throws RpcException {
		checkPermissions(CreateCustomer);
		
		Customer result = super.create(object);
		
		logCreate(result);
		
		return result;
	}

	@Override
	public Customer update(Customer object) throws RpcException {
		checkPermissions(UpdateCustomer);
		
		Customer existing = checkExisting(object.getId());
		Customer result = super.update(object);
		
		logUpdate(existing, result);
		
		return result;
	}
	
	@Override
	public Customer delete(Customer object) throws RpcException {
		checkPermissions(DeleteCustomer);
		
		Customer result = super.delete(object);
		
		logDelete(result);
		
		return result;
	}

	@SuppressWarnings({ "unchecked" })
	@Override
	public List<Customer> getCustomerNameAndIdOnly() throws RpcException {
		checkPermissions(ViewCustomer);
		
		ISimpleCriteria crit = SimpleCriteria.forClass(Customer.class);
		String[] properties = new String[]{ "id", "code", "name" };
		crit.properties(properties); // Retrieve only id, code, and name
		crit.asc("name");
		crit.setResultTransformer(SimpleTransformer.create(Customer.class, properties));
		
		List<Customer> list = getTemplate().findByCriteria(crit.getDetached());
		return list;
	}
	
	@Override
	public PagingLoadResult<TransactionEntry> getTransactions(TransactionLoadConfig config) throws RpcException {
		// Get the transaction entries
		List<TransactionEntry> entries = findByCriteria( TransactionHelper.transactionEntry(config) );
		
		// Compute for the starting balances
		List<TransactionEntry> previousEntries = findByCriteria( TransactionHelper.previousTransactionEntries(config) );
		entries.addAll( TransactionHelper.getStartingBalances(config, previousEntries) );
		
		// Sort by Customer name (since we can't sort it by customer name via SQL)
		Collections.sort(entries, new Comparator<TransactionEntry>() {
			@Override
			public int compare(TransactionEntry arg0, TransactionEntry arg1) {
				Customer c0 = arg0.getCustomer();
				Customer c1 = arg1.getCustomer();
				int diff = c0.getName().compareToIgnoreCase(c1.getName());
				if (diff != 0)
					return diff;
				return arg0.compareTo(arg1);
			}
		});
		
		boolean hasSalesInvoice = CollectionUtils.containsAny(entries, "getType", TransactionType.SALES_INVOICE);
		Map<String, SalesInvoice> invoicesMap = new HashMap<String, SalesInvoice>();
		if (hasSalesInvoice) {
			invoicesMap = getSalesInvoices(config);
		}
		
		double balance = 0; // The running balance (for each customer) 
		Long customerId = null;
		
		for (TransactionEntry entry : entries) {
			Customer customer = entry.getCustomer();
			TransactionType type = entry.getType();
			
			// Reset for each new customer
			if (customerId == null || !customerId.equals(customer.getId())) {
				balance = 0;
				customerId = customer.getId();
			}
			
			if (type == TransactionType.STARTING_BALANCE) {
				balance = entry.getBalance();
				entry.setBalance(balance);
			} else {
				balance += (entry.getDebit() - entry.getCredit());
				entry.setBalance(balance);
				
				if (entry.getType() == TransactionType.SALES_INVOICE) {
					String refNo = entry.getTransactionRefNo();
					SalesInvoice invoice = invoicesMap.get(entry.getTransactionRefNo());
					if (invoice == null)
						continue;
					
					if (invoice.getCollectionStatus() == CollectionStatus.FULLY_PAID) {
						refNo += " x";
					} else if (invoice.getCollectionStatus() == CollectionStatus.PARTIALLY_PAID) {
						refNo += " *";
					}
					
					entry.setTransactionRefNo(refNo);
				}
			}
		}
		
		PagingLoadResultBean<TransactionEntry> result = new PagingLoadResultBean<TransactionEntry>(entries, entries.size(), config.getOffset());
		return result;
	}
	
	// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
	
	@SuppressWarnings("unchecked")
	private Map<String, SalesInvoice> getSalesInvoices(TransactionLoadConfig config) {
		ISimpleCriteria crit = SimpleCriteria.forClass(SalesInvoice.class);
		
		if (!CollectionUtils.isNullOrEmpty(config.getCompanies())) {
			List<Long> companyIds = CollectionUtils.listFrom(config.getCompanies(), "getId");
			crit.in("company.id", companyIds);
		}
		
		if (!CollectionUtils.isNullOrEmpty(config.getCustomers())) {
			List<Long> customerIds = CollectionUtils.listFrom(config.getCustomers(), "getId");
			crit.in("customer.id", customerIds);
		}
		
		List<SalesInvoice> list = invoiceManager.getTemplate().findByCriteria(crit.getDetached());
		return CollectionUtils.mapBy(list, "getGroupAndNo");
	}
	
}
